﻿using System;
using System.Linq;
using System.Linq.Expressions;
using ExpressionVisitor = Magiq.Support.ExpressionVisitor;

namespace Magiq.Providers.Sql.Query
{
    public class ExpressionParameterShifter : ExpressionVisitor
    {
        private readonly Expression expression;
        private readonly ParameterExpression parameter;

        public ExpressionParameterShifter(ParameterExpression parameter, Expression expression)
        {
            this.parameter = parameter;
            this.expression = expression;
        }

        private Expression GetExpression(Expression e)
        {
            return e == parameter ? expression : e;
        }

        protected override Expression VisitConstant(ConstantExpression expression)
        {
            return expression;
        }

        protected override Expression VisitParameter(ParameterExpression expression)
        {
            if (expression == parameter)
                throw new InvalidOperationException("The parameter couldn{t be shifted");
            return expression;
        }

        protected override Expression VisitLambda(LambdaExpression expression)
        {
            return Expression.Lambda(Visit(expression.Body), expression.Parameters.ToArray());
        }

        protected override Expression VisitNewArrayBounds(NewArrayExpression expression)
        {
            return Expression.NewArrayBounds(expression.Type, VisitMany(expression.Expressions));
        }

        protected override Expression VisitNewArrayInit(NewArrayExpression expression)
        {
            return Expression.NewArrayInit(expression.Type, VisitMany(expression.Expressions));
        }

        protected override MemberBinding Binding(MemberBinding binding)
        {
            switch (binding.BindingType)
            {
                case MemberBindingType.Assignment:
                    return Expression.Bind(binding.Member, GetExpression(((MemberAssignment) binding).Expression));
                case MemberBindingType.MemberBinding:
                    return Expression.MemberBind(binding.Member,
                                                 ((MemberMemberBinding) binding).Bindings.Select(x => Binding(x)));
                case MemberBindingType.ListBinding:
                    return Expression.ListBind(binding.Member,
                                               ((MemberListBinding) binding).Initializers.Select(
                                                   VisitElementInit));
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        protected override Expression VisitMemberInit(MemberInitExpression expression)
        {
            return Expression.MemberInit((NewExpression) VisitNew(expression.NewExpression), expression.Bindings.Select(x => Binding(x)));
        }

        protected override Expression VisitListInit(ListInitExpression expression)
        {
            return Expression.ListInit((NewExpression) VisitNew(expression.NewExpression),
                                       expression.Initializers
                                           .Select(VisitElementInit));
        }

        protected override ElementInit VisitElementInit(ElementInit elementInit)
        {
            return Expression.ElementInit(elementInit.AddMethod, VisitMany(elementInit.Arguments));
        }

        protected override Expression VisitNew(NewExpression expression)
        {
            return Expression.New(expression.Constructor, VisitMany(expression.Arguments), expression.Members);
        }


        protected override Expression VisitInvoke(InvocationExpression expression)
        {
            return Expression.Invoke(Visit(expression.Expression),
                                     expression.Arguments.Select(x => Visit(x)));
        }

        protected override Expression VisitConditional(ConditionalExpression expression)
        {
            return Expression.Condition(Visit(expression.Test),
                                        Visit(expression.IfTrue),
                                        Visit(expression.IfFalse));
        }

        protected override Expression VisitBinary(BinaryExpression expression)
        {
            return Expression.MakeBinary(expression.NodeType,
                                         Visit(expression.Left),
                                         Visit(expression.Right),
                                         expression.IsLiftedToNull,
                                         expression.Method,
                                         expression.Conversion);
        }

        protected override Expression VisitUnary(UnaryExpression expression)
        {
            return Expression.MakeUnary(expression.NodeType,
                                        Visit(expression.Operand), expression.Type,
                                        expression.Method);
        }

        protected override Expression VisitMember(MemberExpression expression)
        {
            return Expression.MakeMemberAccess(Visit(expression.Expression), expression.Member);
        }

        protected override Expression VisitTypeIs(TypeBinaryExpression expression)
        {
            return Expression.TypeIs(Visit(expression.Expression), expression.TypeOperand);
        }

        protected override Expression VisitCall(MethodCallExpression expression)
        {
            return Expression.Call(expression.Object == null ? null : Visit(expression.Object),
                                   expression.Method,
                                   expression.Arguments.Select(Visit));
        }

        protected override Expression Dispatch(Expression expression)
        {
            return base.Dispatch(GetExpression(expression));
        }
    }
}